package models.crawlers.common;

import models.crawlers.events.*;
import models.entities.CategoryEntity;
import models.entities.ProductEntity;
import org.apache.commons.lang.NotImplementedException;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

public abstract class ShopCrawler extends Observable {
    protected String crawlerMainUrl;
    public boolean suspended;
    private DocumentCache cachedDoc;
    protected WebSiteInfo webSiteInfo;
    Map<String, String> cookiesFromLastResponse;
    protected String proxyAddress = "http://www.proxplaza.com";
    protected boolean useProxy = false;
    protected String getSourceUrl(String url) throws UnsupportedEncodingException {
        if (useProxy) {
            Map<String, List<String>> params = getUrlParameters(url);
            url = params.get("u").get(0);
        } else {
            url = crawlerMainUrl + url;
        }
        return url;
    }
    public void start(WebSiteInfo siteInfo) throws Exception {
        try {
            if (suspended)
                return;
            if (siteInfo == null) {
                this.webSiteInfo = initializeWebSiteInfo();
            } else {
                this.webSiteInfo = siteInfo;
            }
            notifyObservers(webSiteInfo);
            handleCategories(this.webSiteInfo.currentSitePath);
            NotifyIsDone();
        } catch (Exception ex) {
            webSiteInfo.errorMessage = ex.getMessage();
            webSiteInfo.isError = true;
            notifyObservers(webSiteInfo);
            throw ex;
        }
    }
    private void NotifyIsDone() {
        webSiteInfo.isCompleted = true;
        notifyObservers(new ScanCompleted(webSiteInfo, suspended));
    }
    protected abstract WebSiteInfo initializeWebSiteInfo() throws IOException;
    public abstract List<CategoryInfo> getCategories(String url) throws IOException;
    public abstract List<ProductInfo> getProducts(String url, int pageNumber) throws IOException;
    public abstract ProductEntity getProduct(String url) throws Exception;
    protected abstract String getProductSourceId(String url);
    protected abstract Integer findNextPage(Integer pageNumber, String url) throws IOException;
    protected abstract int getCategoryProductsCount(String url) throws IOException;

    protected void handleProducts(SitePathInfo sitePath) throws Exception {
        sitePath.itemsCount = getCategoryProductsCount(sitePath.url);
        sitePath.containsProducts = true;
        Integer pageNumber = sitePath.pageNumber;
        while (pageNumber != null) {
            List<ProductInfo> products = getProducts(sitePath.url, pageNumber);
            for (ProductInfo productInfo : products) {
                try {
                    if (suspended) {
                        if (sitePath.childPathElement != null)
                            sitePath.childPathElement.isProcessed = false;
                        return;
                    }
                    if (productInfo.title.isEmpty()) {
                        continue;
                    }
                    if (sitePath.childPathElement != null) {
                        sitePath.childPathElement.isProduct = true;
                        if (!sitePath.childPathElement.isProcessed) {
                            if (!sitePath.childPathElement.url.equals(productInfo.url))
                                continue;
                        } else {
                            webSiteInfo.productIndex++;
                            sitePath.childIndex++;
                        }
                    } else {
                        webSiteInfo.productIndex++;
                        sitePath.childIndex++;
                    }
                    if (sitePath.childPathElement == null)
                        sitePath.childPathElement = new SitePathInfo();
                    sitePath.childPathElement.isProcessed = false;
                    sitePath.childPathElement.url = productInfo.url;
                    sitePath.childPathElement.isProduct = true;
                    sitePath.childPathElement.title = productInfo.title;
                    notifyObservers(webSiteInfo);
                    ProductEntity product = getProduct(productInfo.url);
                    product.categoryId = sitePath.id;
                    notifyObservers(new ProductProcessed(product, productInfo.url, getProductSourceId(productInfo.url)));
                    sitePath.childPathElement.id = product.id;
                    sitePath.childPathElement.isProcessed = true;

                } catch (ProductParsingException ex) {
                    notifyObservers(new ProductParsingError(sitePath.url, ex.getMessage()));
                    throw ex;
                }
            }
            pageNumber = findNextPage(pageNumber, sitePath.url);
            sitePath.pageNumber = pageNumber;
        }
    }
    protected void handleCategories(SitePathInfo sitePath) throws Exception {
        List<CategoryInfo> categories = getCategories(sitePath.url);
        sitePath.itemsCount = categories.size();
        if (sitePath.itemsCount > 0) {
            for (CategoryInfo categoryInfo : categories) {
                if (sitePath.childPathElement != null) {
                    if (!sitePath.childPathElement.url.equals(categoryInfo.url)) {
                        continue;
                    }
                } else {
                    sitePath.childIndex++;
                    webSiteInfo.categoryIndex++;
                }
                CategoryEntity category = new CategoryEntity(categoryInfo.title, sitePath.id == null ? 0 : sitePath.id);
                notifyObservers(new CategoryProcessed(category));
                if (sitePath.childPathElement == null) {
                    sitePath.childPathElement = new SitePathInfo();
                }
                sitePath.childPathElement.url = categoryInfo.url;
                sitePath.childPathElement.id = category.id;
                sitePath.childPathElement.title = categoryInfo.title;
                notifyObservers(webSiteInfo);
                handleCategories(sitePath.childPathElement);
                if (suspended)
                    return;
                sitePath.childPathElement = null;
            }
        } else {
            handleProducts(sitePath);
        }
        if (suspended)
            return;
    }
    public void notifyObservers(Object eventObject) {
        setChanged();
        super.notifyObservers(eventObject);
    }
    public void start() throws Exception {
        start(null);
    }
    public void stop() {
        this.suspended = true;
    }
    protected Document downloadPage(String url) throws IOException {
        return downloadPage(url, null);
    }
    protected Map<String, List<String>> getUrlParameters(String url)
            throws UnsupportedEncodingException {
        Map<String, List<String>> params = new HashMap<String, List<String>>();
        String[] urlParts = url.split("\\?");
        if (urlParts.length > 1) {
            String query = urlParts[1];
            for (String param : query.split("&")) {
                String pair[] = param.split("=");
                String key = URLDecoder.decode(pair[0], "UTF-8");
                String value = "";
                if (pair.length > 1) {
                    value = URLDecoder.decode(pair[1], "UTF-8");
                }
                List<String> values = params.get(key);
                if (values == null) {
                    values = new ArrayList<String>();
                    params.put(key, values);
                }
                values.add(value);
            }
        }
        return params;
    }
    protected Document downloadPage(String url, Map<String, String> siteCookies) throws IOException {
        if (cachedDoc != null && cachedDoc.cacheUrl.equals(url)) {
            return cachedDoc.document;
        } else {
            Document doc;
            if (siteCookies != null) {
                if (!useProxy)  {
                    Connection conn = Jsoup.connect(url).timeout(500000);
                    setCookies(conn, siteCookies);
                    doc = conn.get();
                }
                else
                    throw new NotImplementedException("This behavior is not implemented");
            } else {
                if (useProxy && cookiesFromLastResponse == null) {
                    initProxy();
                }
                String body = getPageBody(url);
                if (body.contains("<title>ProxPlaza.com || Free, Anonymously and Safe!</title>")){
                    initProxy();
                    body = getPageBody(url);
                }
                doc = Jsoup.parse(body);
            }

            cachedDoc = new DocumentCache(url, doc);
            return doc;
        }
    }

    protected void setCookies(Connection conn, Map<String, String> siteCookies){
        if (siteCookies != null){
            for (String key : siteCookies.keySet()){
                conn.cookie(key, siteCookies.get(key));
            }
        }
    }

    private String getPageBody(String url) throws IOException {
        Connection conn = Jsoup.connect(getUrlWithProxy(url)).timeout(500000);
        setCookies(conn, cookiesFromLastResponse);
        Connection.Response response = conn.execute();
        cookiesFromLastResponse = response.cookies();
        return response.body();
    }
    private String getUrlWithProxy(String url) throws UnsupportedEncodingException {
        if (useProxy){
            url = String.format("%1$s/browse.php?u=%2$s&b=4", proxyAddress, url);
        }
        return url;
    }
    private void initProxy() throws IOException {
        Connection proxyConn = Jsoup.connect(proxyAddress);
        Connection.Response proxyR = proxyConn.execute();
        cookiesFromLastResponse = proxyR.cookies();
    }

    protected BufferedImage getPictureData(String remoteUrl) throws IOException {
        if (remoteUrl == "")
            return null;
        URL url = new URL(remoteUrl);
        try {
            BufferedImage image = ImageIO.read(url);
            return image;
        } catch (IOException ex) {
            notifyObservers(new DownloadImageError(remoteUrl));
            return null;
        }
    }

}
